package org.papervision3d.objects.primitives {
	import org.papervision3d.Papervision3D;
	import org.papervision3d.core.geom.*;
	import org.papervision3d.core.geom.renderables.Triangle3D;
	import org.papervision3d.core.geom.renderables.Vertex3D;
	import org.papervision3d.core.log.PaperLogger;
	import org.papervision3d.core.math.Number3D;
	import org.papervision3d.core.math.NumberUV;
	import org.papervision3d.core.proto.*;
	import org.papervision3d.materials.utils.MaterialsList;	

	/**
	* <span lang="ja">Cube は 3D オブジェクトの立方体を作成するクラスです。</span>
	* 
	* <span lang="en">The Cube class lets you create and display flat rectangle objects.</span>
	* <p/>
	* <span lang="en">The rectangle can be divided in smaller segments. This is usually done to reduce linear mapping artifacts.</span>
	* <p/>
	* <span lang="en">Dividing the Cube in the direction of the perspective or vanishing point, 
	* helps to reduce this problem. Perspective distortion dissapears when the Cube is facing straignt to the camera, 
	* i.e. it is perpendicular with the vanishing point of the scene.</span>
	*/
	public class Cube extends TriangleMesh3D
	{
		/**
		* <span lang="ja">セグメントの数を指定します。デフォルトは 1 です。</span>
		* <span lang="en">Number of segments per axis. Defaults to 1.</span>
		*/
		public var segments :Number3D;
	
		/**
		* <span lang="ja">立方体の表示する面を指定する定数です。この定数では、面を選択しません。</span>
		* <span lang="en">No faces selected.</span>
		*/
		static public var NONE   :int = 0x00;
	
		/**
		* <span lang="ja">立方体の表示する面を指定する定数です。この定数では、前面を選択します。</span>
		* <span lang="en">Front face selection</span>
		*/
		static public var FRONT  :int = 0x01;
	
		/**
		* <span lang="ja">立方体の表示する面を指定する定数です。この定数では、背面を選択します。</span>
		* <span lang="en">Back face selection</span>
		*/
		static public var BACK   :int = 0x02;
	
		/**
		* <span lang="ja">立方体の表示する面を指定する定数です。この定数では、右面を選択します。</span>
		* <span lang="en">Right face selection</span>
		*/
		static public var RIGHT  :int = 0x04;
	
		/**
		* <span lang="ja">立方体の表示する面を指定する定数です。この定数では、左面を選択します。</span>
		* <span lang="en">Left face selection</span>
		*/
		static public var LEFT   :int = 0x08;
	
		/**
		* <span lang="ja">立方体の表示する面を指定する定数です。この定数では、上面を選択します。</span>
		* <span lang="en">Top face selection</span>
		*/
		static public var TOP    :int = 0x10;
	
		/**
		* <span lang="ja">立方体の表示する面を指定する定数です。この定数では、底面を選択します。</span>
		* <span lang="en">Bottom face selection</span>
		*/
		static public var BOTTOM :int = 0x20;
	
		/**
		* <span lang="ja">立方体の表示する面を指定する定数です。この定数では、全ての面を選択します。</span>
		* <span lang="en">All faces selected.</span>
		*/
		static public var ALL    :int = FRONT + BACK + RIGHT + LEFT + TOP + BOTTOM;
		
		private var insideFaces  :int;
		private var excludeFaces :int;
	
		// ___________________________________________________________________________________________________
		//                                                                                               N E W
		// NN  NN EEEEEE WW    WW
		// NNN NN EE     WW WW WW
		// NNNNNN EEEE   WWWWWWWW
		// NN NNN EE     WWW  WWW
		// NN  NN EEEEEE WW    WW
	
		/**
		* <span lang="ja">新しい Cube インスタンスを作成します。</span>
		* <span lang="en">Create a new Cube object.</span>
		* <p/>
		* @param	materials	
		* <span lang="ja">テクスチャーとして適用したいマテリアルのプロパティーを MaterialsList を利用して指定します。</span>
		* <span lang="en">A MaterialObject3D object that contains the material properties of the object.</span>
		* <span lang="ja">サポートされるマテリアルは front, back, right, left, top, bottom, all です。 例えば次のような記述で指定します。 </span>
		* 
		* <div class="listing">
		* <pre>
		*	var materials:MaterialsList = new MaterialsList(
		*	{
		*		all:	new MovieAssetMaterial( "Front", true ), // これはデフォルトのマテリアルです。
		*		front:  new MovieAssetMaterial( "Front", true ),
		*		back:   new MovieAssetMaterial( "Back", true ),
		*		right:  new MovieAssetMaterial( "Right", true ),
		*		left:   new MovieAssetMaterial( "Left", true ),
		*		top:    new MovieAssetMaterial( "Top", true ),
		*		bottom: new MovieAssetMaterial( "Bottom", true )
		*	} );
		* </pre>
		* </div>
		* 
		* @param	width			
		* <span lang="ja">横幅です。</span>
		* <span lang="en">Desired width.</span>
		* 
		* @param	depth			[optional] -
		* <span lang="ja">深さです。</span>
		* <span lang="en">Desired depth.</span>
		* 
		* @param	height			[optional] -
		* <span lang="ja">高さです。</span>
		* <span lang="en">Desired height.</span>
		* 
		* @param	segmentsS		
		* <span lang="ja">水平(width)方向セグメント数です。デフォルトは 1 です。</span>
		* <span lang="en">Number of segments sagitally (plane perpendicular to width). Defaults to 1.</span>
		* 
		* @param	segmentsT		
		* <span lang="ja">深さ(depth)方向のセグメント数です。デフォルトは 1 です。</span>
		* <span lang="en">Number of segments transversally (plane perpendicular to depth). Defaults to segmentsS.</span>
		* 
		* @param	segmentsH		
		* <span lang="ja">高さ(height)方向のセグメント数です。デフォルトは 1 です。</span>
		* <span lang="en">Number of segments horizontally (plane perpendicular to height). Defaults to segmentsS.</span>
		* 
		* @param	insideFaces		
		* <span lang="ja">立方体の内側の面で表示したい面を指定します。デフォルトは Cube.NONE で、表示される面はありません。</span>
		* <span lang="en">Faces that are visible from the inside. Defaults to Cube.NONE.</span>
		*
		* <span lang="ja">表示したい面は次の書式で指定します。</span>
		* <span lang="en">You can add or sustract faces to your selection. For examples: Cube.FRONT+Cube.BACK or Cube.ALL-Cube.Top.</span>
		* 
		* <div class="listing"><pre>Cube.FRONT+Cube.BACK or Cube.ALL-Cube.Top.</pre></div>
		* 
		* 
		* @param	excludeFaces	
		* <span lang="ja">立方体の外側の面で除外したい面を指定します。デフォルトは Cube.NONE で、すべての面が表示されます。</span>
		* <span lang="en">Faces that will not be created. Defaults to Cube.NONE.</span>
		* 
		* <span lang="ja">除外したい面は次の書式で指定します。</span>
		* <span lang="en">You can add or sustract faces to your selection. For examples: Cube.FRONT+Cube.BACK or Cube.ALL-Cube.Top.</span>
		* 
		* <div class="listing"><pre>Cube.FRONT+Cube.BACK or Cube.ALL-Cube.Top.</pre></div>
		* 
		* @see org.papervision3d.materials.utils.MaterialsList
		* 
		* @example 次の例では Cube を作成し、3Dシーンに Cube を表示します。
		* <div class="listing"><pre>
		* import org.papervision3d.objects.primitives.Cone;
		* import org.papervision3d.materials.WireframeMaterial;
		* import org.papervision3d.materials.utils.MaterialsList;
		* 
		* // マテリアルを作成
		* var material:WireframeMaterial = new WireframeMaterial(0x0000FF);
		* 
		* var ml:MaterialsList = new MaterialsList({all : material});
		* 
		* // プリミティブオブジェクトを作成
		* var obj:Cube = new Cube(ml, 100, 100, 100, 5, 5, 5);
		* scene.addChild(obj);
		* </pre></div>
		* ※scene は BasicView クラスのプロパティー
		*/
		public function Cube( materials:MaterialsList, width:Number=500, depth:Number=500, height:Number=500, segmentsS:int=1, segmentsT:int=1, segmentsH:int=1, insideFaces:int=0, excludeFaces:int=0 )
		{
			super( materials.getMaterialByName( "all" ), new Array(), new Array(), null );
			
			this.materials = materials;
			
			this.insideFaces  = insideFaces;
			this.excludeFaces = excludeFaces;
	
			segments = new Number3D( segmentsS, segmentsT, segmentsH );
	
			buildCube( width, height, depth );
		}
	
		protected function buildCube( width:Number, height:Number, depth:Number ):void
		{
			var width2  :Number = width  /2;
			var height2 :Number = height /2;
			var depth2  :Number = depth  /2;
			
			if( ! (excludeFaces & FRONT) )
				buildPlane( "front", "x", "y", width, height, depth2, ! Boolean( insideFaces & FRONT ) );
	
			if( ! (excludeFaces & BACK) )
				buildPlane( "back", "x", "y", width, height, -depth2, Boolean( insideFaces & BACK ) );
	
			if( ! (excludeFaces & RIGHT) )
				buildPlane( "right", "z", "y", depth, height, width2, Boolean( insideFaces & RIGHT ) );
	
			if( ! (excludeFaces & LEFT) )
				buildPlane( "left", "z", "y", depth, height, -width2, ! Boolean( insideFaces & LEFT ) );
	
			if( ! (excludeFaces & TOP) )
				buildPlane( "top", "x", "z", width, depth, height2, Boolean( insideFaces & TOP ) );
	
			if( ! (excludeFaces & BOTTOM) )
				buildPlane( "bottom", "x", "z", width, depth, -height2, ! Boolean( insideFaces & BOTTOM ) );

			mergeVertices();
			
			for each(var t:Triangle3D in this.geometry.faces){
				t.renderCommand.create = createRenderTriangle;
			}
			
			this.geometry.ready = true;
			
			if(Papervision3D.useRIGHTHANDED)
				this.geometry.flipFaces();
		}
	
		protected function buildPlane( mat:String, u:String, v:String, width:Number, height:Number, depth:Number, reverse:Boolean=false ):void
		{
			var matInstance:MaterialObject3D;
			if( ! (matInstance= materials.getMaterialByName( mat )))
			{
				if(!(matInstance=materials.getMaterialByName( "all" ))){
					PaperLogger.warning( "Required material not found in given materials list. Supported materials are: front, back, right, left, top, bottom & all." );
					return;
				}
				
			}
			
			matInstance.registerObject(this); // needed for the shaders.
			// Find w depth axis
			var w :String;
			if( (u=="x" && v=="y") || (u=="y" && v=="x") ) w = "z";
			else if( (u=="x" && v=="z") || (u=="z" && v=="x") ) w = "y";
			else if( (u=="z" && v=="y") || (u=="y" && v=="z") ) w = "x";
	
			// Mirror
			var rev :Number = reverse? -1 : 1;
	
			// Build plane
			var gridU    :Number = this.segments[ u ];
			var gridV    :Number = this.segments[ v ];
			var gridU1   :Number = gridU + 1;
			var gridV1   :Number = gridV + 1;
	
			var vertices   :Array = this.geometry.vertices;
			var faces      :Array = this.geometry.faces;
			var planeVerts :Array = new Array();
	
			var textureU :Number = width /2;
			var textureV :Number = height /2;
	
			var incU     :Number = width / gridU;
			var incV     :Number = height / gridV;
	
			// Vertices
			for( var iu:int = 0; iu < gridU1; iu++ )
			{
				for( var iv:int = 0; iv < gridV1; iv++ )
				{
					var vertex:Vertex3D = new Vertex3D();
					
					vertex[ u ] = (iu * incU - textureU) * rev;
					vertex[ v ] = iv * incV - textureV;
					vertex[ w ] = depth;
					
					vertices.push( vertex );
					planeVerts.push( vertex );
				}
			}
	
			// Faces
			var uvA :NumberUV;
			var uvC :NumberUV;
			var uvB :NumberUV;
	
			for(  iu = 0; iu < gridU; iu++ )
			{
				for(  iv= 0; iv < gridV; iv++ )
				{
					// Triangle A
					var a:Vertex3D = planeVerts[ iu     * gridV1 + iv     ];
					var c:Vertex3D = planeVerts[ iu     * gridV1 + (iv+1) ];
					var b:Vertex3D = planeVerts[ (iu+1) * gridV1 + iv     ];
	
					uvA =  new NumberUV( iu     / gridU, iv     / gridV );
					uvC =  new NumberUV( iu     / gridU, (iv+1) / gridV );
					uvB =  new NumberUV( (iu+1) / gridU, iv     / gridV );
	
					faces.push(new Triangle3D(this, [ a, b, c ], matInstance, [ uvA, uvB, uvC ] ) );
	
					// Triangle B
					a = planeVerts[ (iu+1) * gridV1 + (iv+1) ];
					c = planeVerts[ (iu+1) * gridV1 + iv     ];
					b = planeVerts[ iu     * gridV1 + (iv+1) ];
	
					uvA =  new NumberUV( (iu+1) / gridU, (iv+1) / gridV );
					uvC =  new NumberUV( (iu+1) / gridU, iv     / gridV );
					uvB =  new NumberUV( iu     / gridU, (iv+1) / gridV );
	
					faces.push(new Triangle3D(this, [ c, a, b ], matInstance, [ uvC, uvA, uvB ] ) );
				}
			}
		}
		
		/**
		 * 登録されたマテリアルの参照を破棄します。
		 */
		public function destroy():void
		{
			var mat:MaterialObject3D;
			for each(mat in materials){
				mat.unregisterObject(this);
			}
		}
		
		
	}
}
